package cn.lijiajia3515.cairo.example3.config;


import cn.lijiajia3515.cairo.security.oauth2.resource.server.web.CairoBearerTokenAuthenticationEntryPoint;
import cn.lijiajia3515.cairo.security.oauth2.resource.server.web.CairoOAuth2HttpMessageAuthenticationFailedHandler;
import cn.lijiajia3515.cairo.security.web.authentication.CairoHttpMessageAccessDeniedHandler;
import cn.lijiajia3515.cairo.security.web.authentication.CairoHttpMessageAuthenticationSuccessHandler;
import cn.lijiajia3515.cairo.security.web.authentication.CairoLogAuthenticationEventListener;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationFilter;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.util.StringUtils;

import java.util.List;
import java.util.regex.Pattern;

@Configuration(proxyBeanMethods = false)
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true)
public class SecurityConfig {

	@Configuration(proxyBeanMethods = false)
	public static class UserDetailsServiceAutoConfiguration {

		private static final String NOOP_PASSWORD_PREFIX = "{noop}";

		private final Pattern PASSWORD_ALGORITHM_PATTERN = Pattern.compile("^\\{.+}.*$");

		private final Log logger = LogFactory.getLog(org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration.class);

		@Bean
		public InMemoryUserDetailsManager inMemoryUserDetailsManager(SecurityProperties properties,
																	 ObjectProvider<PasswordEncoder> passwordEncoder) {
			SecurityProperties.User user = properties.getUser();
			List<String> roles = user.getRoles();
			return new InMemoryUserDetailsManager(
				User.withUsername(user.getName()).password(getOrDeducePassword(user, passwordEncoder.getIfAvailable()))
					.roles(StringUtils.toStringArray(roles)).build());
		}

		private String getOrDeducePassword(SecurityProperties.User user, PasswordEncoder encoder) {
			String password = user.getPassword();
			if (user.isPasswordGenerated()) {
				logger.info(String.format("%n%nUsing generated security password: %s%n", user.getPassword()));
			}
			if (encoder != null || PASSWORD_ALGORITHM_PATTERN.matcher(password).matches()) {
				return password;
			}
			return NOOP_PASSWORD_PREFIX + password;
		}

	}

	@Bean
	CairoLogAuthenticationEventListener cairoLogAuthenticationEventListener() {
		return new CairoLogAuthenticationEventListener();
	}

	@Bean
	@Order(1000)
	SecurityFilterChain mySecurity(HttpSecurity http,
								   // CairoHttpMessageAuthenticationEntryPoint httpMessageAuthenticationEntryPoint,
								   // CairoHttpMessageAuthenticationFailedHandler httpMessageAuthenticationFailedHandler,
								   CairoHttpMessageAccessDeniedHandler httpMessageAccessDeniedHandler,
								   CairoHttpMessageAuthenticationSuccessHandler httpMessageAuthenticationSuccessHandler,
								   CairoBearerTokenAuthenticationEntryPoint bearerTokenAuthenticationEntryPoint,
								   CairoOAuth2HttpMessageAuthenticationFailedHandler oAuth2HttpMessageAuthenticationFailedHandler) throws Exception {
		return http
			.csrf().disable()
			.authorizeRequests(authorizeRequests -> authorizeRequests
				.mvcMatchers("/**").permitAll()
			)
			.exceptionHandling()
			.authenticationEntryPoint((request, response, authException) -> {
				throw authException;
			})
			.accessDeniedHandler((request, response, accessDeniedException) -> {
				throw accessDeniedException;
			})
			.and()

			.csrf().disable()
			.sessionManagement().disable()

			.formLogin()
			.failureHandler(oAuth2HttpMessageAuthenticationFailedHandler)
			.successHandler(httpMessageAuthenticationSuccessHandler)
			.withObjectPostProcessor(new ObjectPostProcessor<UsernamePasswordAuthenticationFilter>() {
				@Override
				public <O extends UsernamePasswordAuthenticationFilter> O postProcess(O object) {
					System.out.println("object = " + object.getFilterConfig());
					return object;
				}
			})
			.and()

			.oauth2ResourceServer()
			.authenticationEntryPoint(bearerTokenAuthenticationEntryPoint)
			.accessDeniedHandler(httpMessageAccessDeniedHandler)
			.withObjectPostProcessor(new ObjectPostProcessor<BearerTokenAuthenticationFilter>() {
				@Override
				public <O extends BearerTokenAuthenticationFilter> O postProcess(O object) {
					object.setAuthenticationFailureHandler(oAuth2HttpMessageAuthenticationFailedHandler);
					return object;
				}
			})
			.jwt()
			.and()
			.and()
			.build();
	}

	@Bean
	WebSecurityCustomizer webSecurityCustomizer() {
		return (web) -> web.ignoring().antMatchers("/", "/actuator/**", "/favicon.ico", "**/favicon.ico", "/public/**");
	}
}
